﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using AnjLab.FX.Sys;
using AnjLab.SyncIT.Core.Api;
using AnjLab.SyncIT.Core.Data.Outlook;
using Exception=System.Exception;

namespace AnjLab.SyncIT.Core
{
    public class SyncProcess
    {
        public double Progress { get; set;}
        public bool InProgress { get; set;}
        private static readonly SyncProcess _instance = new SyncProcess();
        public event EventHandler<EventArgs<Source, string>> SourceSynced;

        public static SyncProcess Instance {
            get { return _instance; }
        }

        public void Start(IEnumerable<Source> sources)
        {
            if (InProgress)
                return;
            ThreadPool.QueueUserWorkItem(delegate { Sync( sources); });
        }

        public void Sync(IEnumerable<Source> sources) {
            lock (_instance) {
                try {
                    InProgress = true;
                    Library.Log.Info("Start syncing...");
                    
                    var count = sources.Count();
                    if (count == 0)
                    {
                        return;
                    }

                    IEnumerable<Source> copy = sources.ToList();
                    Progress = 0;
                    var processed = 0d;
                    foreach (var source in copy) {
                        try {
                            Sync(source, 1d/count);
                        } catch (Exception x) {
                            Library.Log.Error("Error during syncing {0}", x);
                        }
                        processed++;
                        Progress = processed/count;
                    }
                } finally {
                    InProgress = false;
                    Library.Log.Info("Finished syncing.");
                }
            }
        }

        private void Sync(Source source, double progressIncInterval) {
            if (source.Paused)
                return;
            Library.Log.Info("Syncing {0}", source.Title);

            var outlookDao = new OutlookTicketDao();
            using(var taskDao = source.GetTaskDao())
            try {
                var outlookOpenedTasks = outlookDao.FindOpenedItems(source);
                Library.Log.Debug("{0} tasks have status opened in Outlook for source \"{1}\"",
                    outlookOpenedTasks.Count, source.ProjectBase);

                var outlookChangedTasks = outlookDao.FindModifiedItems(source);
                Library.Log.Debug("{0} tasks have changed in Outlook since \"{1}\"",
                    outlookChangedTasks.Count, source.SyncTimestamp);

                var sourceChangedTasks = taskDao.GetChangedTasks();
                Library.Log.Debug("{0} tasks have changed/are opened in the source \"{1}\"",
                    sourceChangedTasks.Length, source.ProjectBase);

                var skipTasks = new List<string>();

                //  Back sync
                foreach (var task in outlookChangedTasks)
                {
                    if (!sourceChangedTasks.Contains(task.ItemID))
                    {
                        //  Update the task in the source
                        var sourceTask = taskDao.Get(task.ItemID);

                        if (sourceTask == null)
                        {
                            Library.Log.Warning("Can't find the task \"{0}\" (taskID = {1}) "
                                                + "in the source \"{2}\". Looks like its a new task; "
                                                + "adding new tasks through Outlook isn't currently supported",
                                                task.Subject, task.ItemID, source.ProjectBase);

                            skipTasks.Add(task.ItemID);

                            continue;
                        }

                        outlookDao.UpdateSourceTaskFromOutlook(source, sourceTask, task);
                    }
                }

                //  Close outlook tasks if they were closed in the source
                if (!taskDao.CanTrackTaskLifetime)
                {
                    foreach (var task in outlookOpenedTasks)
                    {
                        if (skipTasks.Contains(task.ItemID))
                        {
                            continue;
                        }

                        if (!sourceChangedTasks.Contains(task.ItemID))
                        {
                            Library.Log.Debug(
                                "The task \"{0}\" doesn't belongs to the user anymore "
                                + "or it has been closed in the source",
                                task.Subject);
                            task.Close();
                        }
                    }
                }

                //  Adding more granularity to the progress bar
                double tasksCount = sourceChangedTasks.Length;
                double tasksProcessed = 0;
                double initialProgress = Progress;

                //  Add new tasks to Outlook or update tasks that has already been added previously
                //  or make back syncronization if previously added tasks were changed within Outlook
                foreach (var taskID in sourceChangedTasks) {
                    var task = taskDao.Get(taskID);

                    outlookDao.CreateOrUpdateItem(source, task);

                    tasksProcessed++;
                    Progress = initialProgress + (tasksProcessed/tasksCount)*progressIncInterval;
                }

                //  Since Outlook tasks may change during the sync we need to be sure that
                //  TaskItem.LastModificationTime in this case would be less then sync timeStamp
                var timeStamp = DateTime.Now;

                source.SyncTimestamp = timeStamp;
                OnSourceSynced(source, "succesfuly synced");

            } catch(Exception x) {
                Library.Log.Error("Error syncing {0}: {1}", source.Title, x);
            }
        }

        private void OnSourceSynced(Source source, string message) {
            if (SourceSynced != null)
                SourceSynced(this, EventArg.New(source, message));
        }

    }
}
